﻿using System;
using WiMo.Games;
using WiMo.Games.Drawables;
using WiMo.Games.Menus;
using WiMo.Games.Inputs;
using WiMo.Games.Audio;
using System.Collections.Generic;

namespace YABOG
{
    class Game : IGame
    {
        private const int INT_PADDLE_TOP = 240;
        private const int INT_OUT_OF_BOUNDS_TOP = 300;
        private const int INT_POINTS_PER_BRICK = 20;
        private const int MNU_CONTINUE = 100;
        private const int MNU_SETTHEME = 102;
        private const int MNU_SOUND_ON = 103;
        private const int MNU_ACCELEROMETER = 104;
        private const int MNU_DIAGNOSTICS = 101;
        private const int MNU_EXIT = 999;
        private const int INT_ROWS_OF_BRICKS = 6;
        private const int INT_BRICKS_IN_ROWS = 9;
        private const int INT_INITIAL_BALL_SPEED = 140;
        private const int INT_PADDLE_MOVEMENT = 120;
        private const int INT_BRICK_LEFT_OFFSET = 10;
        private const int INT_BRICK_TOP_OFFSET = 50;
        private const string STR_CurrentTheme = "CurrentTheme";
        private const string STR_SoundOn = "SoundOn";
        private const string STR_UseAccelerometer = "Accelerometer";
        private bool _brickHit = false;
        private bool _isManual = false;

        private enum GameStates
        {
            StartUp,
            Active,
            WaitToLaunchBall,
            BetweenRounds,
            GameOver,
        }

        IMenuComponent _mainMenu;

        private int _round;
        private int _score;
        private int _balls;
        private IInputManager _lastState;
        private GameStates _gameState;
        IMenu _themeMenu;
        IMenu _soundOnMenu;
        IMenu _useAccelerometerMenu;
        ISpriteImage _splashScreen;
        ISpriteImage _paddle;
        ISpriteImage _ball;
        ISpriteImage _screenBackground;
        ISpriteShape _bottom;
        ISpriteFont _titleFont;
        ISpriteText _scoreDisplay;
        IGameAudio _blip;
        ISpriteImage[] _bricks;

        bool _soundOn;        

        struct Theme
        {
            public int Index;
            public string Name;
            public Color TextColor;
        }

        private Theme[] Themes = new Theme[] { 
            new Theme { Index = 0, Name = "Street", TextColor = Color.White }, 
            new Theme { Index = 1, Name = "Starfield", TextColor = Color.White },
            new Theme { Index = 2, Name = "Beach", TextColor = Color.Black },
            new Theme { Index = 3, Name = "Soccer", TextColor = Color.White },
            new Theme { Index = 4, Name = "Email Blast", TextColor = Color.Yellow }/*,
            new Theme { Index = 5, Name = "Metal", TextColor = Color.White }*/
        };

        private Theme _currentTheme;

        public Game(IGameEngine engine)
        {
            Engine = engine;
            Engine.ContentManager.ContentLocation = "Content";
            Engine.DiagnosticsFontColor = Color.Yellow;
            Engine.Display.BackgroundColor = Color.Black;
            Engine.InputManager.SoftMenuFontColor = Color.White;
            Engine.EnableDiagnostics = false;
            Engine.InputManager.UseAccelerometerAsThumbPad = true;
            Engine.DiagnosticsLocation = new PointF(10, 15);
            Engine.Display.DisplayStrategy = DisplayStrategies.MostlyStatic;

            _bricks = new ISpriteImage[INT_ROWS_OF_BRICKS * INT_BRICKS_IN_ROWS];
        }

        public void Initialize()
        {

        }

        public void BeginRun()
        {
            _gameState = GameStates.StartUp;
            Engine.Pause();
        }

        public void CreateMenus()
        {
            var themeIndex = Convert.ToInt32(Engine.StateManager.Get(STR_CurrentTheme, "0"));
            _soundOn = Convert.ToBoolean(Engine.StateManager.Get(STR_SoundOn, "true"));
            Engine.InputManager.UseAccelerometerAsThumbPad = Convert.ToBoolean(Engine.StateManager.Get(STR_UseAccelerometer, "true"));
            
            _currentTheme = Themes[themeIndex];
            Engine.DiagnosticsFontColor = _currentTheme.TextColor;

            _mainMenu = Engine.MenuManager.Create("Main Menu");
            
            _mainMenu.MenuSelected += _mainMenu_MenuSelected;
            _mainMenu.AddMenu("Resume", MNU_CONTINUE);
            _themeMenu = _mainMenu.AddMenu(String.Format("Theme: {0}", _currentTheme.Name), MNU_SETTHEME);
            _mainMenu.AddMenu("Diagnostics", MNU_DIAGNOSTICS);
            _soundOnMenu = _mainMenu.AddMenu(string.Format("Sound: {0}", _soundOn ? "On" : "Off"), MNU_SOUND_ON);
            if(Engine.InputManager.SupportAccelerometer)
                _useAccelerometerMenu = _mainMenu.AddMenu(string.Format("Accelerometer: {0}", Engine.InputManager.UseAccelerometerAsThumbPad ? "On" : "Off"), MNU_ACCELEROMETER);

            _mainMenu.AddMenu("End Game", MNU_EXIT);

            Engine.RegisterComponent(_mainMenu);
        }

        private void UpdateThemeIndex()
        {
            var themeIndex = _currentTheme.Index + 1;
            if (themeIndex == Themes.Length)
                themeIndex = 0;

            foreach (var brick in _bricks)
                brick.ImageIndex = themeIndex;

            _currentTheme = Themes[themeIndex];
            _themeMenu.Text = string.Format("Theme: {0}", _currentTheme.Name);
            _screenBackground.ImageIndex = themeIndex;
            _ball.ImageIndex = themeIndex;
            _paddle.ImageIndex = themeIndex;
            Engine.DiagnosticsFontColor = _currentTheme.TextColor;

            Engine.StateManager.Store(STR_CurrentTheme, themeIndex.ToString());
        }

        void _mainMenu_MenuSelected(object sender, SelectedMenuEventArgs e)
        {
            switch (e.SelectedMenu.MenuId)
            {
                case MNU_SOUND_ON:
                    _soundOn = !_soundOn;
                    _soundOnMenu.Text = string.Format("Sound: {0}", _soundOn ? "On" : "Off");
                    Engine.StateManager.Store(STR_SoundOn, _soundOn.ToString()); 
                    break;
                case MNU_ACCELEROMETER:
                    Engine.InputManager.UseAccelerometerAsThumbPad = !Engine.InputManager.UseAccelerometerAsThumbPad;
                    _useAccelerometerMenu.Text = string.Format("Accelerometer: {0}", Engine.InputManager.UseAccelerometerAsThumbPad ? "On" : "Off");
                    Engine.StateManager.Store(STR_UseAccelerometer, Engine.InputManager.UseAccelerometerAsThumbPad.ToString());

                    break;
                case MNU_SETTHEME:
                    UpdateThemeIndex();

                    break;
                case MNU_CONTINUE:
                    _mainMenu.Hide();
                    Engine.Resume();
                    break;
                case MNU_DIAGNOSTICS:
                    Engine.EnableDiagnostics = !Engine.EnableDiagnostics;
                    break;
                case MNU_EXIT:
                    _mainMenu.Hide();
                    Engine.Exit();
                break;
            }
        }

        public void LoadContent()
        {
            var themeIndex = Convert.ToInt32(Engine.StateManager.Get(STR_CurrentTheme, "0"));

            _splashScreen = Engine.ContentManager.LoadImage("SplashScreen");
            _splashScreen.Origin = Origin.TopLeft;

            _screenBackground = Engine.ContentManager.LoadImage("ScreenBackground");
            _screenBackground.AddImage(Engine.ContentManager, "StarBackground");
            _screenBackground.AddImage(Engine.ContentManager, "BeachBackground");
            _screenBackground.AddImage(Engine.ContentManager, "SoccerBackground");
            _screenBackground.AddImage(Engine.ContentManager, "HighTechBackground");
      //      _screenBackground.AddImage(Engine.ContentManager, "MetalBackground");
            _screenBackground.Origin = Origin.TopLeft;
            _screenBackground.ImageIndex = themeIndex;

            _paddle = Engine.ContentManager.LoadImage("StreetPaddle");
            _paddle.AddImage(Engine.ContentManager, "StarfieldPaddle");
            _paddle.AddImage(Engine.ContentManager, "BeachPaddle");
            _paddle.AddImage(Engine.ContentManager, "SoccerPaddle");
            _paddle.AddImage(Engine.ContentManager, "HighTechPaddle");
        //    _paddle.AddImage(Engine.ContentManager, "MetalPaddle");
            _paddle.ImageIndex = themeIndex;
            _paddle.Origin = Origin.Center;
            _paddle.Position = new PointF(Engine.Display.TargetSize.Width / 2, INT_PADDLE_TOP);
            _paddle.CollisionCategory = CollisionCategory.One;
            _paddle.IsStatic = false;
            _paddle.ColorKey = Color.White;
            _paddle.RenderStrategy = RenderStrategy.Quality;
            _paddle.CollisionShape = CollisionShapes.Rectangle;
            _paddle.CanCollidesWithList.Add(CollisionCategory.One);

            Engine.PhysicsManager.Add(_paddle);
            Engine.CollisionManager.Add(_paddle);

            _ball = Engine.ContentManager.LoadImage("StreetBall");
            _ball.AddImage(Engine.ContentManager, "StarfieldBall");
            _ball.AddImage(Engine.ContentManager, "BeachBall");
            _ball.AddImage(Engine.ContentManager, "SoccerBall");
            _ball.AddImage(Engine.ContentManager, "StreetBall");
          //  _ball.AddImage(Engine.ContentManager, "MetalBall");
            _ball.ImageIndex = themeIndex;
            _ball.RenderStrategy = RenderStrategy.Quality;
            _ball.IsStatic = false;
            _ball.Position = new PointF(60, 140);
            _ball.Force = (new Vector2(40, -INT_INITIAL_BALL_SPEED));
            _ball.ColorKey = Color.White;
            _ball.Collision += _ball_Collision;
            _ball.CollisionShape = CollisionShapes.Sphere;

            _ball.CanCollidesWithList.Add(CollisionCategory.One);
            _ball.CanCollidesWithList.Add(CollisionCategory.Two);
            _ball.CanCollidesWithList.Add(CollisionCategory.Three);

            Engine.PhysicsManager.Add(_ball);
            Engine.CollisionManager.Add(_ball);

            for (var row = 0; row < INT_ROWS_OF_BRICKS; ++row)
            {
                for (var col = 0; col < INT_BRICKS_IN_ROWS; ++col)
                {
                    var brickIndex = row * INT_BRICKS_IN_ROWS + col;
                    var brick = Engine.ContentManager.LoadImage("StreetBrick");
                    brick.ColorKey = Color.White;
                    brick.AddImage(Engine.ContentManager, "StarfieldBrick");
                    brick.AddImage(Engine.ContentManager, "BeachBrick");
                    brick.AddImage(Engine.ContentManager, "SoccerBrick");
                    brick.AddImage(Engine.ContentManager, "HighTechBrick");
                    //brick.AddImage(Engine.ContentManager, "MetalBrick");
                    brick.ImageIndex = themeIndex;
                    brick.Position = new PointF(((brick.UnscaledSize.Width + 3) * col) + INT_BRICK_LEFT_OFFSET, ((brick.UnscaledSize.Height + 5) * row) + INT_BRICK_TOP_OFFSET);
                    brick.Origin = Origin.TopLeft;
                    brick.CollisionCategory = CollisionCategory.Two;
                    brick.CollisionShape = CollisionShapes.Rectangle;
                    brick.IsStatic = true;
                    Engine.PhysicsManager.Add(brick);
                    Engine.CollisionManager.Add(brick);
                    _bricks[brickIndex] = brick;
                }
            }

            _titleFont = Engine.ContentManager.GetFont("ArialLarge");
            _titleFont.Name = "Arial";
            _titleFont.Size = 24;

            _bottom = Engine.AssetFactory.CreateRectangle();
            _bottom.Origin = Origin.TopLeft;
            _bottom.Position = new PointF(0, INT_OUT_OF_BOUNDS_TOP);
            _bottom.Size = new Size(240, 20);
            _bottom.CollisionCategory = CollisionCategory.Three;

            _blip = Engine.ContentManager.LoadAudio("blip");

            _scoreDisplay = Engine.AssetFactory.CreateText();
            _scoreDisplay.Position = new PointF(100, 10);
            _scoreDisplay.Size = new Size(120, 20);
            _scoreDisplay.Font = Engine.DefaultFont;
            _scoreDisplay.ForeColor = Color.White;
            _scoreDisplay.Text = "NOT SET";             

            Engine.CollisionManager.Add(_bottom);
        }

        void BallDropped()
        {
            _balls--;

            _scoreDisplay.Text = string.Format("Score: {0} Ball: {1}", _score, _balls);

            ResetBall();
            Engine.Pause();

            if (_balls != 0)
            {
                _gameState = GameStates.WaitToLaunchBall;
                return;
            }

            _gameState = GameStates.GameOver;
            Engine.Pause();
        }

        void EndRound()
        {
            _gameState = GameStates.BetweenRounds;
            ++_round;

            Engine.Pause();
        }

        void _ball_Collision(object sender, CollisionEventArgs e)
        {
            if (e.CollisionType != CollisionEventArgs.CollisionTypes.Border)
            {
                switch (e.Sprite.CollisionCategory)
                {
                    case CollisionCategory.Three:
                        BallDropped();
                        break;

                    case CollisionCategory.Two:
                        //Only allow one brick hit per update.
                        if (!_brickHit)
                        {
                            var brick = e.Sprite as ISpriteImage;
                            brick.IsVisible = false;
                            _score += INT_POINTS_PER_BRICK;

                            _scoreDisplay.Text = string.Format("Score: {0} Ball: {1}", _score, _balls);

                            _ball.Force = new Vector2(_ball.Force.X, -_ball.Force.Y);
                            _brickHit = true;
                            if(_soundOn)
                                _blip.Play();
                        }

                        foreach (var brickAlive in _bricks)
                            if (brickAlive.IsVisible)
                                return;

                        EndRound();

                        break;
                    case CollisionCategory.One:
                        _ball.Force = new Vector2(_ball.Force.X, -_ball.Force.Y);
                        break;
                }
            }
        }

        private void ResetBall()
        {
            _ball.Position = new PointF(_paddle.UnscaledPosition.X + _paddle.UnscaledSize.Width / 2, _paddle.UnscaledPosition.Y - 20);
            _ball.Force = new Vector2(40, -INT_INITIAL_BALL_SPEED);
        }

        void ConfigureBricks(int configuration)
        {
            int[] bricksToRemove = null;

            switch (configuration)
            {
                case 0:
                    bricksToRemove = new int[27];
                    for (var idx = 0; idx < 27; ++idx)
                        bricksToRemove[idx] = idx + 27;
                    break;
                case 1:
                    bricksToRemove = new int[] { 0, 1, 7, 8, 9, 17, 30, 31, 32, 38, 39, 40, 41, 42, 46, 47, 48, 49, 50, 51, 52 };
                    break;
                case 2:
                    bricksToRemove = new int[] { 0, 1, 2, 6, 7, 8, 9, 10, 16, 17, 18, 22, 26, 29, 30, 31, 32, 33, 36, 40, 44, 45, 46, 52, 53 };
                    break;
                case 3:
                    bricksToRemove = new int[] { 0, 1, 2, 4, 6, 7, 8, 9, 10, 16, 17, 18, 26, 29, 30, 31, 32, 33, 36, 38, 42, 44, 45, 46, 52, 53 };
                    break;
                case 4:
                    bricksToRemove = new int[] { 10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23, 24, 25, 28, 29, 30, 31, 32, 33, 34, 37, 38, 39, 40, 41, 42, 43 };
                    break;
                case 5:
                    bricksToRemove = new int[] { 3, 4, 5, 10, 16, 21, 27, 29, 35, 36, 37, 38, 39, 41, 42, 43, 44, 45, 46, 52, 53 };
                    break;
                case 6:
                    bricksToRemove = new int[] { 1, 2, 4, 6, 7, 13, 18, 21, 22, 23, 26, 27, 30, 31, 32, 35, 40, 46, 47, 49, 51, 52 };
                    break;
                case 7:
                    bricksToRemove = new int[] { 10, 11, 13, 15, 16, 28, 29, 31, 33, 34, 45, 46, 47, 48, 49, 50, 51, 52, 53 };
                    break;
                case 8:
                    bricksToRemove = new int[] { 1, 2, 6, 7, 9, 11, 13, 15, 17, 18, 19, 22, 25, 26, 27, 28, 29, 31, 33, 34, 35, 47, 48, 50, 51 };
                    break;
                case 9:
                    bricksToRemove = new int[] { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53 };
                    break;
            }

            foreach (var brick in bricksToRemove)
                _bricks[brick].IsVisible = false;

        }

        private void StartRound()
        {
            foreach (var brick in _bricks)
            {
                brick.CollisionList.Clear();
                brick.IsVisible = true;
            }

            ConfigureBricks(_round - 1 % 10);

            ResetBall();

            _gameState = GameStates.Active;
            Engine.Resume();
        }

        private void StartGame()
        {
            _score = 0;
            _balls = 5;
            _round = 1;

            _scoreDisplay.Text = string.Format("Score: {0} Ball: {1}", _score, _balls);

            _gameState = GameStates.WaitToLaunchBall;
            Engine.Pause();

            StartRound();
        }

        public void Update(GameTime gameTime)
        {
            _brickHit = false;

            switch (_gameState)
            {
                case GameStates .WaitToLaunchBall:
                    if (Engine.InputManager.WasScreenTouched(_lastState))
                    {
                        Engine.Resume();
                        _gameState = GameStates.Active;
                    }
                    break;
                case GameStates.StartUp:
                    if (Engine.InputManager.WasPressed(_lastState, Keys.Return)
                        || Engine.InputManager.WasScreenTouched(_lastState))
                        StartGame();
                    break;
                case GameStates.BetweenRounds:
                    if (Engine.InputManager.WasScreenTouched(_lastState))
                        StartRound();

                    if (Engine.InputManager.WasPressed(_lastState, Keys.LeftSoftButton))
                        StartRound();

                    if (Engine.InputManager.WasPressed(_lastState, Keys.RightSoftButton))
                        Engine.Exit();

                    Engine.InputManager.RightSoftButton = "Exit";
                    Engine.InputManager.LeftSoftButton = "Continue";
                    Engine.InputManager.MenuBarBackgroundColor = new Color(Color.Black, 0x7f);

                    break;

                case GameStates.GameOver:
                    if (Engine.InputManager.WasPressed(_lastState, Keys.RightSoftButton))
                        Engine.Exit();

                    if (Engine.InputManager.WasScreenTouched(_lastState))
                        StartGame();

                    if (Engine.InputManager.WasPressed(_lastState, Keys.LeftSoftButton))
                        StartGame();

                    Engine.InputManager.RightSoftButton = "Exit";
                    Engine.InputManager.LeftSoftButton = "Restart";
                    Engine.InputManager.MenuBarBackgroundColor = new Color(Color.Black, 0x7f);

                    break;
                case GameStates.Active:
                    ProcessUpdate_ActiveState();
                    break;
            }

            _lastState = Engine.InputManager.GetKeysState();
        }

        private void ProcessUpdate_ActiveState()
        {
            if (Engine.InputManager.WasPressed(_lastState, Keys.F1) && !_mainMenu.IsVisible)
            {
                Engine.Pause();
                _mainMenu.Reset();
                _mainMenu.Show();
            }

            if (Engine.InputManager.WasPressed(_lastState, Keys.RightSoftButton))
                Engine.Exit();

            if (!Engine.IsPaused)
            {
                if (Engine.InputManager.IsKeyDown(Keys.Left))
                {
                    _paddle.Force = new Vector2(-INT_PADDLE_MOVEMENT, 0);
                }
                else if (Engine.InputManager.IsKeyDown(Keys.Right))
                {
                    _paddle.Force = new Vector2(INT_PADDLE_MOVEMENT, 0);
                }
                else
                    _paddle.Force = Vector2.Zero;
            }

            _isManual = !Engine.InputManager.IsMouseUp;

            foreach (var touchPoint in Engine.InputManager.TouchPoints)
            {
                if (touchPoint.State == TouchPoint.TouchLocationState.Pressed || touchPoint.State == TouchPoint.TouchLocationState.Moved)
                {
                    var upperBounds = _paddle.UnscaledPosition.Y + 10;
                    var lowerBounds = _paddle.UnscaledPosition.Y + 50;
                    if (upperBounds < touchPoint.Position.Y && lowerBounds > touchPoint.Position.Y )
                    {
                        _paddle.Position = new PointF(touchPoint.Position.X, INT_PADDLE_TOP);
                        _isManual = true;
                    }
                }
            }

            Engine.InputManager.RightSoftButton = "Exit";
            Engine.InputManager.LeftSoftButton = "Main Menu";
            Engine.InputManager.MenuBarBackgroundColor = new Color(Color.Black, 0x7f);
        }

        ISpriteText _roundText;
        ISpriteText _scoreText;
        ISpriteText _tapToContinue;
        ISpriteText _gameOver;

        public void Draw(GameTime gameTime)
        {
            switch (_gameState)
            {
                case GameStates.StartUp:
                    Engine.Display.BackgroundImage = _splashScreen.TextureList[_splashScreen.ImageIndex];
                    break;
                case GameStates.GameOver:
                    Engine.Display.BackgroundImage = _screenBackground.TextureList[_screenBackground.ImageIndex];

                    //Engine.Display.DrawText(new PointF(Engine.Display.TargetSize.Width / 2, 40), "Game Over", _titleFont, _currentTheme.TextColor, Origin.Center);
                    //Engine.Display.DrawText(new PointF(Engine.Display.TargetSize.Width / 2, 110), string.Format("Round {0}", _round), _titleFont, _currentTheme.TextColor, Origin.Center);
                    //Engine.Display.DrawText(new PointF(Engine.Display.TargetSize.Width / 2, 240), string.Format("Score {0}", _score), _titleFont, _currentTheme.TextColor, Origin.Center);

                    break;
                case GameStates.BetweenRounds:
                    Engine.Display.BackgroundImage = _screenBackground.TextureList[_screenBackground.ImageIndex];

                    //Engine.Display.DrawText(new PointF(Engine.Display.TargetSize.Width / 2, 40), string.Format("Round {0}", _round), _titleFont, _currentTheme.TextColor, Origin.Center);
                    //Engine.Display.DrawText(new PointF(Engine.Display.TargetSize.Width / 2, 110), string.Format("Score {0}", _score), _titleFont, _currentTheme.TextColor, Origin.Center);
                    //Engine.Display.DrawText(new PointF(Engine.Display.TargetSize.Width / 2, 180), string.Format("Tap to Continue", _score, _balls), _titleFont, _currentTheme.TextColor, Origin.Center);

                    break;
                case GameStates.WaitToLaunchBall:
                case GameStates.Active:
                    Engine.Display.BackgroundImage = _screenBackground.TextureList[_screenBackground.ImageIndex];
                    Engine.Display.Add(_scoreDisplay);
                    Engine.Display.Add(_ball);
                    Engine.Display.Add(_paddle);

                    foreach (var bone in _bricks)
                    {
                        if (bone.IsVisible)
                            Engine.Display.Add(bone);
                    }

                    //if (_isManual)
                    //    Engine.Display.DrawCircle(new PointF(_paddle.UnscaledPosition.X, _paddle.UnscaledSize.Height / 2 + _paddle.UnscaledPosition.Y + 12), 12, Color.Red, 2);
                    break;
            }

            //if(_gameState == GameStates.WaitToLaunchBall)
            //    Engine.Display.DrawText(new PointF(Engine.Display.TargetSize.Width / 2, 180), "Tap to Continue", _titleFont, _currentTheme.TextColor, Origin.Center);
        }

        public bool CanExit()
        {
            Engine.Exit();

            return true;
        }

        public IGameEngine Engine { get; set; }
    }
}
